﻿using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using OnlinePlots.Diagrams;
using OnlinePlots.Plots.Elements;
using System.Collections.Generic;
using System.Linq;
using OnlinePlots.Plots.Scales;
using System.Collections.Specialized;
using System.Collections;
using System.Globalization;

namespace OnlinePlots.Plots
{
    public class ExplicitFunctionPlotControl : DiagramControl
    {
        private Point oldCursorPosition = new Point();
        private Boolean arePanMouseButtonsDown = false;

        protected DiagramElementCollection<ExplicitFunctionPlotItem> items;

        protected Scale horizontalScale = new LinearScale(1);
        protected Scale verticalScale = new LinearScale(1);
        protected HorizontalLabelStripElement horizontalLabelStrip = new HorizontalLabelStripElement();
        protected VerticalLabelStripElement verticalLabelStrip = new VerticalLabelStripElement();
        protected GridElement horizontalGrid = new GridElement();
        protected GridElement verticalGrid = new GridElement();

        public static DependencyProperty FitLogicalFrameHeightProperty = DependencyProperty.Register(
                "FitLogicalFrameHeight",
                typeof(bool),
                typeof(ExplicitFunctionPlotControl),
                new PropertyMetadata(false)
            );

        /// <summary>
        /// Gets or sets flag indicating whether logical frame height will be automatically
        /// fit to show all plots
        /// </summary>
        public bool FitLogicalFrameHeight
        {
            get { return (bool)GetValue(FitLogicalFrameHeightProperty); }
            set { SetValue(FitLogicalFrameHeightProperty, value); }
        }

        /// <summary>
        /// Creates a new instance of ExplicitFunctionPlotControl control
        /// </summary>
        public ExplicitFunctionPlotControl()
            : base()
        {
            items = new DiagramElementCollection<ExplicitFunctionPlotItem>(diagramElementsContainer);
            new MouseWheelHelper(this).Moved += new EventHandler<OnlinePlots.Diagrams.MouseWheelEventArgs>(OnMouseWheel);

            horizontalLabelStrip.BindToDiagramElement(diagramElementsContainer);
            verticalLabelStrip.BindToDiagramElement(diagramElementsContainer);
            horizontalGrid.BindToDiagramElement(diagramElementsContainer);
            verticalGrid.BindToDiagramElement(diagramElementsContainer);
        }

        /// <summary>
        /// Gets a list of plots loaded into control represented by ExplicitFunctionPlotItem instances
        /// </summary>
        new public DiagramElementCollection<ExplicitFunctionPlotItem> Items
        {
            get
            {
                return items;
            }
        }

        public static DependencyProperty EnableMouseZoomProperty = DependencyProperty.Register(
                "EnableMouseZoom",
                typeof(bool),
                typeof(ExplicitFunctionPlotControl),
                new PropertyMetadata(true)
            );

        /// <summary>
        /// Enables or disables mouse zoom of the plot. By default, the user is allowed
        /// to zoom in/out the plot area by mouse scroll
        /// </summary>
        public Boolean EnableMouseZoom
        {
            get { return (bool)GetValue(EnableMouseZoomProperty); }
            set { SetValue(EnableMouseZoomProperty, value); }
        }

        public static DependencyProperty EnableMousePanProperty = DependencyProperty.Register(
                "EnableMousePan",
                typeof(bool),
                typeof(ExplicitFunctionPlotControl),
                new PropertyMetadata(true)
            );

        /// <summary>
        /// Enables or disables mouse pan of the plot. By default, the user is allowed
        /// to pan the plot area by mouse-drag
        /// </summary>
        public Boolean EnableMousePan
        {
            get { return (bool)GetValue(EnableMousePanProperty); }
            set { SetValue(EnableMousePanProperty, value); }
        }

        public static DependencyProperty MouseZoomFactorProperty = DependencyProperty.Register(
                "MouseZoomFactor",
                typeof(double),
                typeof(ExplicitFunctionPlotControl),
                new PropertyMetadata(1.1)
            );

        /// <summary>
        /// Gets or sets mouse zoom factor which controls the zooming sensitivity
        /// </summary>
        public double MouseZoomFactor
        {
            get { return (double)GetValue(MouseZoomFactorProperty); }
            set { SetValue(MouseZoomFactorProperty, value); }
        }

        /// <summary>
        /// Gets or sets horizontal scale of the plot
        /// </summary>
        public Scale HorizontalScale
        {
            get { return horizontalScale; }
            set { horizontalScale = value; }
        }

        /// <summary>
        /// Gets or sets vertical scale of the plot
        /// </summary>
        public Scale VerticalScale
        {
            get { return verticalScale; }
            set { verticalScale = value; }
        }

        /// <summary>
        /// Gets or sets horizontal label strip of the plot
        /// </summary>
        public HorizontalLabelStripElement HorizontalLabelStrip
        {
            get { return horizontalLabelStrip; }
            set
            {
                horizontalLabelStrip.Unbind();
                horizontalLabelStrip = value;
                horizontalLabelStrip.BindToDiagramElement(diagramElementsContainer);
            }
        }

        /// <summary>
        /// Gets or sets vertical label strip of the plot
        /// </summary>
        public VerticalLabelStripElement VerticalLabelStrip
        {
            get { return verticalLabelStrip; }
            set
            {
                verticalLabelStrip.Unbind();
                verticalLabelStrip = value;
                verticalLabelStrip.BindToDiagramElement(diagramElementsContainer);
            }
        }

        /// <summary>
        /// Gets or sets horizontal grid of the plot
        /// </summary>
        public GridElement HorizontalGrid
        {
            get { return horizontalGrid; }
            set
            {
                horizontalGrid.Unbind();
                horizontalGrid = value;
                horizontalGrid.BindToDiagramElement(diagramElementsContainer);
            }
        }

        /// <summary>
        /// Gets or sets vertical grid of the plot
        /// </summary>
        public GridElement VerticalGrid
        {
            get { return verticalGrid; }
            set
            {
                verticalGrid.Unbind();
                verticalGrid = value;
                verticalGrid.BindToDiagramElement(diagramElementsContainer);
            }
        }

        protected double GetGridStep(double intervalLength, int desiredStepsCount)
        {
            double realStep = intervalLength / desiredStepsCount;
            double minimalStep = Math.Pow(10, Math.Floor(Math.Log10(realStep)));
            double difference = Math.Abs(realStep - minimalStep);
            double result = minimalStep;
            if (difference > Math.Abs(realStep - 2 * minimalStep))
            {
                difference = Math.Abs(realStep - 2 * minimalStep);
                result = 2 * minimalStep;
            }
            if (difference > Math.Abs(realStep - 5 * minimalStep))
            {
                difference = Math.Abs(realStep - 5 * minimalStep);
                result = 5 * minimalStep;
            }
            if (difference > Math.Abs(realStep - 10 * minimalStep))
            {
                difference = Math.Abs(realStep - 10 * minimalStep);
                result = 10 * minimalStep;
            }
            return result;
        }

        protected override void PrepareDiagramShapes()
        {
            if (HorizontalGrid.Visible)
            {
                double horizontalGridInterval = GetGridStep(LogicalFrameHeight, HorizontalGrid.DesiredLineDensity);
                double auxHorizontalGridInterval = horizontalGridInterval / HorizontalGrid.DesiredAuxLineDensity;

                if (LogicalFrameYMin * LogicalFrameYMax > 0 || !HorizontalGrid.DrawGridFromAxisLine)
                {
                    for (double auxHorizontalGridPosition = Math.Ceiling(LogicalFrameYMin / horizontalGridInterval) * horizontalGridInterval - auxHorizontalGridInterval; auxHorizontalGridPosition >= logicalFrameYMin; auxHorizontalGridPosition -= auxHorizontalGridInterval)
                    {
                        HorizontalGrid.AddAuxLine(FromLogicalToPhysical(logicalFrameXMin, auxHorizontalGridPosition), FromLogicalToPhysical(logicalFrameXMax, auxHorizontalGridPosition), VerticalScale.GetScaledValue(auxHorizontalGridPosition));
                    }

                    for (double horizontalGridPosition = Math.Ceiling(logicalFrameYMin / horizontalGridInterval) * horizontalGridInterval; horizontalGridPosition <= logicalFrameYMax; horizontalGridPosition += horizontalGridInterval)
                    {
                        Point pointFrom = FromLogicalToPhysical(logicalFrameXMin, horizontalGridPosition);
                        Point pointTo = FromLogicalToPhysical(logicalFrameXMax, horizontalGridPosition);
                        if (Math.Abs(horizontalGridPosition) < double.Epsilon)
                        {
                            HorizontalGrid.AddBasicLine(pointFrom, pointTo, VerticalScale.GetScaledValue(0));
                        }
                        else
                        {
                            HorizontalGrid.AddBasicLine(pointFrom, pointTo, VerticalScale.GetScaledValue(horizontalGridPosition));
                        }

                        for (double auxHorizontalGridPosition = horizontalGridPosition + auxHorizontalGridInterval; auxHorizontalGridPosition < horizontalGridPosition + horizontalGridInterval && auxHorizontalGridPosition <= logicalFrameYMax; auxHorizontalGridPosition += auxHorizontalGridInterval)
                        {
                            HorizontalGrid.AddAuxLine(FromLogicalToPhysical(logicalFrameXMin, auxHorizontalGridPosition), FromLogicalToPhysical(logicalFrameXMax, auxHorizontalGridPosition), VerticalScale.GetScaledValue(auxHorizontalGridPosition));
                        }
                    }
                }
                else
                {
                    Point pointFrom = FromLogicalToPhysical(logicalFrameXMin, 0);
                    Point pointTo = FromLogicalToPhysical(logicalFrameXMax, 0);

                    HorizontalGrid.AddAxisLine(pointFrom, pointTo, VerticalScale.GetScaledValue(0));

                    for (double auxHorizontalGridPosition = auxHorizontalGridInterval; auxHorizontalGridPosition < horizontalGridInterval && auxHorizontalGridPosition <= logicalFrameYMax; auxHorizontalGridPosition += auxHorizontalGridInterval)
                    {
                        HorizontalGrid.AddAuxLine(FromLogicalToPhysical(logicalFrameXMin, auxHorizontalGridPosition), FromLogicalToPhysical(logicalFrameXMax, auxHorizontalGridPosition), VerticalScale.GetScaledValue(auxHorizontalGridPosition));
                    }

                    for (double auxHorizontalGridPosition = -auxHorizontalGridInterval; auxHorizontalGridPosition > -horizontalGridInterval && auxHorizontalGridPosition >= logicalFrameYMin; auxHorizontalGridPosition -= auxHorizontalGridInterval)
                    {
                        HorizontalGrid.AddAuxLine(FromLogicalToPhysical(logicalFrameXMin, auxHorizontalGridPosition), FromLogicalToPhysical(logicalFrameXMax, auxHorizontalGridPosition), VerticalScale.GetScaledValue(auxHorizontalGridPosition));
                    }

                    for (double horizontalGridPosition = horizontalGridInterval; horizontalGridPosition <= logicalFrameYMax; horizontalGridPosition += horizontalGridInterval)
                    {
                        pointFrom = FromLogicalToPhysical(logicalFrameXMin, horizontalGridPosition);
                        pointTo = FromLogicalToPhysical(logicalFrameXMax, horizontalGridPosition);

                        HorizontalGrid.AddBasicLine(pointFrom, pointTo, VerticalScale.GetScaledValue(horizontalGridPosition));

                        for (double auxHorizontalGridPosition = horizontalGridPosition + auxHorizontalGridInterval; auxHorizontalGridPosition < horizontalGridPosition + horizontalGridInterval && auxHorizontalGridPosition <= logicalFrameYMax; auxHorizontalGridPosition += auxHorizontalGridInterval)
                        {
                            HorizontalGrid.AddAuxLine(FromLogicalToPhysical(logicalFrameXMin, auxHorizontalGridPosition), FromLogicalToPhysical(logicalFrameXMax, auxHorizontalGridPosition), VerticalScale.GetScaledValue(auxHorizontalGridPosition));
                        }
                    }

                    for (double horizontalGridPosition = -horizontalGridInterval; horizontalGridPosition >= logicalFrameYMin; horizontalGridPosition -= horizontalGridInterval)
                    {
                        pointFrom = FromLogicalToPhysical(logicalFrameXMin, horizontalGridPosition);
                        pointTo = FromLogicalToPhysical(logicalFrameXMax, horizontalGridPosition);

                        HorizontalGrid.AddBasicLine(pointFrom, pointTo, VerticalScale.GetScaledValue(horizontalGridPosition));

                        for (double auxHorizontalGridPosition = horizontalGridPosition - auxHorizontalGridInterval; auxHorizontalGridPosition > horizontalGridPosition - horizontalGridInterval && auxHorizontalGridPosition >= logicalFrameYMin; auxHorizontalGridPosition -= auxHorizontalGridInterval)
                        {
                            HorizontalGrid.AddAuxLine(FromLogicalToPhysical(logicalFrameXMin, auxHorizontalGridPosition), FromLogicalToPhysical(logicalFrameXMax, auxHorizontalGridPosition), VerticalScale.GetScaledValue(auxHorizontalGridPosition));
                        }
                    }
                }
            }


            ///////////////////////////////////////////////////////////////////////////////////////////

            if (VerticalGrid.Visible)
            {
                double verticalGridInterval = GetGridStep(LogicalFrameWidth, VerticalGrid.DesiredLineDensity);
                double auxVerticalGridInterval = verticalGridInterval / VerticalGrid.DesiredAuxLineDensity;

                if (LogicalFrameXMin * LogicalFrameXMax > 0 || !VerticalGrid.DrawGridFromAxisLine)
                {
                    for (double auxVerticalGridPosition = Math.Ceiling(logicalFrameXMin / verticalGridInterval) * verticalGridInterval - auxVerticalGridInterval; auxVerticalGridPosition >= logicalFrameXMin; auxVerticalGridPosition -= auxVerticalGridInterval)
                    {
                        VerticalGrid.AddAuxLine(FromLogicalToPhysical(auxVerticalGridPosition, logicalFrameYMin), FromLogicalToPhysical(auxVerticalGridPosition, logicalFrameYMax), HorizontalScale.GetScaledValue(auxVerticalGridPosition));
                    }

                    for (double verticalGridPosition = Math.Ceiling(logicalFrameXMin / verticalGridInterval) * verticalGridInterval; verticalGridPosition <= logicalFrameXMax; verticalGridPosition += verticalGridInterval)
                    {
                        if (Math.Abs(verticalGridPosition) < double.Epsilon)
                        {
                            VerticalGrid.AddAxisLine(FromLogicalToPhysical(0, logicalFrameYMin), FromLogicalToPhysical(0, logicalFrameYMax), HorizontalScale.GetScaledValue(0));
                        }
                        else
                        {
                            VerticalGrid.AddBasicLine(FromLogicalToPhysical(verticalGridPosition, logicalFrameYMin), FromLogicalToPhysical(verticalGridPosition, logicalFrameYMax), HorizontalScale.GetScaledValue(verticalGridPosition));
                        }

                        for (double auxVerticalGridPosition = verticalGridPosition + auxVerticalGridInterval; auxVerticalGridPosition < verticalGridPosition + verticalGridInterval && auxVerticalGridPosition <= logicalFrameXMax; auxVerticalGridPosition += auxVerticalGridInterval)
                        {
                            VerticalGrid.AddAuxLine(FromLogicalToPhysical(auxVerticalGridPosition, logicalFrameYMin), FromLogicalToPhysical(auxVerticalGridPosition, logicalFrameYMax), HorizontalScale.GetScaledValue(auxVerticalGridPosition));
                        }
                    }
                }
                else
                {
                    VerticalGrid.AddAxisLine(FromLogicalToPhysical(0, logicalFrameYMin), FromLogicalToPhysical(0, logicalFrameYMax), VerticalScale.GetScaledValue(0));

                    for (double auxVerticalGridPosition = auxVerticalGridInterval; auxVerticalGridPosition < verticalGridInterval && auxVerticalGridPosition <= logicalFrameXMax; auxVerticalGridPosition += auxVerticalGridInterval)
                    {
                        VerticalGrid.AddAuxLine(FromLogicalToPhysical(auxVerticalGridPosition, logicalFrameYMin), FromLogicalToPhysical(auxVerticalGridPosition, logicalFrameYMax), HorizontalScale.GetScaledValue(auxVerticalGridPosition));
                    }

                    for (double auxVerticalGridPosition = -auxVerticalGridInterval; auxVerticalGridPosition > -verticalGridInterval && auxVerticalGridPosition >= logicalFrameXMin; auxVerticalGridPosition -= auxVerticalGridInterval)
                    {
                        VerticalGrid.AddAuxLine(FromLogicalToPhysical(auxVerticalGridPosition, logicalFrameYMin), FromLogicalToPhysical(auxVerticalGridPosition, logicalFrameYMax), HorizontalScale.GetScaledValue(auxVerticalGridPosition));
                    }

                    for (double verticalGridPosition = verticalGridInterval; verticalGridPosition <= logicalFrameXMax; verticalGridPosition += verticalGridInterval)
                    {
                        VerticalGrid.AddBasicLine(FromLogicalToPhysical(verticalGridPosition, logicalFrameYMin), FromLogicalToPhysical(verticalGridPosition, logicalFrameYMax), HorizontalScale.GetScaledValue(verticalGridPosition));

                        for (double auxVerticalGridPosition = verticalGridPosition + auxVerticalGridInterval; auxVerticalGridPosition < verticalGridPosition + verticalGridInterval && auxVerticalGridPosition <= logicalFrameXMax; auxVerticalGridPosition += auxVerticalGridInterval)
                        {
                            VerticalGrid.AddAuxLine(FromLogicalToPhysical(auxVerticalGridPosition, logicalFrameYMin), FromLogicalToPhysical(auxVerticalGridPosition, logicalFrameYMax), HorizontalScale.GetScaledValue(auxVerticalGridPosition));
                        }
                    }

                    for (double verticalGridPosition = -verticalGridInterval; verticalGridPosition >= logicalFrameXMin; verticalGridPosition -= verticalGridInterval)
                    {
                        VerticalGrid.AddBasicLine(FromLogicalToPhysical(verticalGridPosition, logicalFrameYMin), FromLogicalToPhysical(verticalGridPosition, logicalFrameYMax), HorizontalScale.GetScaledValue(verticalGridPosition));

                        for (double auxVerticalGridPosition = verticalGridPosition - auxVerticalGridInterval; auxVerticalGridPosition > verticalGridPosition - verticalGridInterval && auxVerticalGridPosition >= logicalFrameXMin; auxVerticalGridPosition -= auxVerticalGridInterval)
                        {
                            VerticalGrid.AddAuxLine(FromLogicalToPhysical(auxVerticalGridPosition, logicalFrameYMin), FromLogicalToPhysical(auxVerticalGridPosition, logicalFrameYMax), HorizontalScale.GetScaledValue(auxVerticalGridPosition));
                        }
                    }
                }
            }

            //////////////////////////////////////////////////////////

            if (HorizontalLabelStrip.Visible)
            {
                int verticalLabelPosition = TopMargin;
                switch (HorizontalLabelStrip.Position)
                {
                    case HorizontalLabelStripPosition.UpperBorder:
                        verticalLabelPosition = TopMargin;
                        break;
                    case HorizontalLabelStripPosition.LowerBorder:
                        verticalLabelPosition = (int)root.ActualHeight - BottomMargin;
                        break;
                    case HorizontalLabelStripPosition.AxisGridLine:
                        if (HorizontalGrid.AxisLine != null)
                        {
                            verticalLabelPosition = (int)HorizontalGrid.AxisLine.Y1;
                        }
                        break;
                    case HorizontalLabelStripPosition.HighestGridLine:
                        Line highestHorizontalGridLine = null;
                        foreach (Line horizontalGridLine in HorizontalGrid.BasicLines)
                        {
                            if (highestHorizontalGridLine == null || horizontalGridLine.Y1 < highestHorizontalGridLine.Y1)
                            {
                                highestHorizontalGridLine = horizontalGridLine;
                            }
                        }
                        if (highestHorizontalGridLine != null)
                        {
                            verticalLabelPosition = (int)highestHorizontalGridLine.Y1;
                        }
                        break;
                    case HorizontalLabelStripPosition.LowestGridLine:
                        Line lowestHorizontalGridLine = null;
                        foreach (Line horizontalGridLine in HorizontalGrid.BasicLines)
                        {
                            if (lowestHorizontalGridLine == null || horizontalGridLine.Y1 > lowestHorizontalGridLine.Y1)
                            {
                                lowestHorizontalGridLine = horizontalGridLine;
                            }
                        }
                        if (lowestHorizontalGridLine != null)
                        {
                            verticalLabelPosition = (int)lowestHorizontalGridLine.Y1;
                        }
                        break;
                }

                if (VerticalGrid.AxisLine != null)
                {
                    HorizontalLabelStrip.AddLabel(
                    new Point(VerticalGrid.AxisLine.X1, verticalLabelPosition),
                    (double)VerticalGrid.AxisLine.Tag,
                    ((double)VerticalGrid.AxisLine.Tag).ToString("G14", CultureInfo.InvariantCulture));
                }

                foreach (Line verticalGridLine in VerticalGrid.BasicLines)
                {
                    HorizontalLabelStrip.AddLabel(
                    new Point(verticalGridLine.X1, verticalLabelPosition),
                    (double)verticalGridLine.Tag,
                    ((double)verticalGridLine.Tag).ToString("G14", CultureInfo.InvariantCulture));
                }
            }

            ////////////////////////////////////////////////////////

            if (VerticalLabelStrip.Visible)
            {
                int horizontalLabelPosition = LeftMargin;
                switch (VerticalLabelStrip.Position)
                {
                    case VerticalLabelStripPosition.LeftBorder:
                        horizontalLabelPosition = LeftMargin;
                        break;
                    case VerticalLabelStripPosition.RightBorder:
                        horizontalLabelPosition = (int)root.ActualWidth - RightMargin;
                        break;
                    case VerticalLabelStripPosition.AxisGridLine:
                        if (VerticalGrid.AxisLine != null)
                        {
                            horizontalLabelPosition = (int)VerticalGrid.AxisLine.X1;
                        }
                        break;
                    case VerticalLabelStripPosition.LeftmostGridLine:
                        Line leftmostVerticalGridLine = null;
                        foreach (Line verticalGridLine in VerticalGrid.BasicLines)
                        {
                            if (leftmostVerticalGridLine == null || verticalGridLine.X1 < leftmostVerticalGridLine.X1)
                            {
                                leftmostVerticalGridLine = verticalGridLine;
                            }
                        }
                        if (leftmostVerticalGridLine != null)
                        {
                            horizontalLabelPosition = (int)leftmostVerticalGridLine.X1;
                        }
                        break;
                    case VerticalLabelStripPosition.RightmostGridLine:
                        Line rightmostVerticalGridLine = null;
                        foreach (Line verticalGridLine in VerticalGrid.BasicLines)
                        {
                            if (rightmostVerticalGridLine == null || verticalGridLine.X1 > rightmostVerticalGridLine.X1)
                            {
                                rightmostVerticalGridLine = verticalGridLine;
                            }
                        }
                        if (rightmostVerticalGridLine != null)
                        {
                            horizontalLabelPosition = (int)rightmostVerticalGridLine.X1;
                        }
                        break;
                }

                if (HorizontalGrid.AxisLine != null)
                {
                    VerticalLabelStrip.AddLabel(
                    new Point(horizontalLabelPosition, HorizontalGrid.AxisLine.Y1),
                    (double)HorizontalGrid.AxisLine.Tag,
                    ((double)HorizontalGrid.AxisLine.Tag).ToString("G14", CultureInfo.InvariantCulture));
                }

                foreach (Line horizontalGridLine in HorizontalGrid.BasicLines)
                {
                    VerticalLabelStrip.AddLabel(
                    new Point(horizontalLabelPosition, horizontalGridLine.Y1),
                    (double)horizontalGridLine.Tag,
                    ((double)horizontalGridLine.Tag).ToString("G14", CultureInfo.InvariantCulture));
                }
            }

            List<Line> horizontalGridLines = new List<Line>(HorizontalGrid.BasicLines);
            if (HorizontalGrid.AxisLine != null) horizontalGridLines.Add(HorizontalGrid.AxisLine);
            List<TextBlock> verticalGridLabels = new List<TextBlock>(VerticalLabelStrip.Labels);
            if (horizontalGridLines.Count > 1 && verticalGridLabels.Count > 1)
            {
                horizontalGridLines.Sort(delegate(Line a, Line b) { return ((double)a.Tag).CompareTo((double)b.Tag); });
                verticalGridLabels.Sort(delegate(TextBlock a, TextBlock b) { return ((double)a.Tag).CompareTo((double)b.Tag); });

                int axisLineIndex = -1;
                try
                {
                    axisLineIndex = horizontalGridLines.IndexOf(horizontalGridLines.First(line => (double)line.Tag == 0));
                }
                catch (InvalidOperationException) { }

                int maxLabelHeight = 1;
                foreach (TextBlock verticalGridLabel in verticalGridLabels)
                {
                    maxLabelHeight = Math.Max((int)verticalGridLabel.Height, maxLabelHeight);
                }
                int distanceBetweenLinesInPixels = (int)Math.Abs(horizontalGridLines[0].Y1 - horizontalGridLines[1].Y1);
                int labeledGridLinesStep = Math.Max(1, (int)Math.Ceiling((double)(maxLabelHeight + 5) / distanceBetweenLinesInPixels));
                if (axisLineIndex == -1)
                {
                    for (int i = 0; i < verticalGridLabels.Count; ++i)
                    {
                        verticalGridLabels[i].Visibility = i % labeledGridLinesStep == 0 ? Visibility.Visible : Visibility.Collapsed;
                    }
                }
                else
                {
                    for (int i = 1; i + axisLineIndex < verticalGridLabels.Count; ++i)
                    {
                        verticalGridLabels[i + axisLineIndex].Visibility = i % labeledGridLinesStep == 0 ? Visibility.Visible : Visibility.Collapsed;
                    }
                    for (int i = 1; axisLineIndex - i >= 0; ++i)
                    {
                        verticalGridLabels[axisLineIndex - i].Visibility = i % labeledGridLinesStep == 0 ? Visibility.Visible : Visibility.Collapsed;
                    }
                }
            }

            List<Line> verticalGridLines = new List<Line>(VerticalGrid.BasicLines);
            if (VerticalGrid.AxisLine != null) verticalGridLines.Add(VerticalGrid.AxisLine);
            List<TextBlock> horizontalGridLabels = new List<TextBlock>(HorizontalLabelStrip.Labels);
            if (verticalGridLines.Count > 1 && horizontalGridLabels.Count > 1)
            {
                verticalGridLines.Sort(delegate(Line a, Line b) { return ((double)a.Tag).CompareTo((double)b.Tag); });
                horizontalGridLabels.Sort(delegate(TextBlock a, TextBlock b) { return ((double)a.Tag).CompareTo((double)b.Tag); });

                int axisLineIndex = -1;
                try
                {
                    axisLineIndex = verticalGridLines.IndexOf(verticalGridLines.First(line => (double)line.Tag == 0));
                }
                catch (InvalidOperationException) { }
                int maxLabelWidth = 1;
                foreach (TextBlock horizontalGridLabel in horizontalGridLabels)
                {
                    maxLabelWidth = Math.Max((int)horizontalGridLabel.Width, maxLabelWidth);
                }
                int distanceBetweenLinesInPixels = (int)Math.Abs(verticalGridLines[0].X1 - verticalGridLines[1].X1);
                int labeledGridLinesStep = Math.Max(1, (int)Math.Ceiling((double)(maxLabelWidth + 5) / distanceBetweenLinesInPixels));
                if (axisLineIndex == -1)
                {
                    for (int i = 0; i < horizontalGridLabels.Count; ++i)
                    {
                        horizontalGridLabels[i].Visibility = i % labeledGridLinesStep == 0 ? Visibility.Visible : Visibility.Collapsed;
                    }
                }
                else
                {
                    for (int i = 1; i + axisLineIndex < horizontalGridLabels.Count; ++i)
                    {
                        horizontalGridLabels[i + axisLineIndex].Visibility = i % labeledGridLinesStep == 0 ? Visibility.Visible : Visibility.Collapsed;
                    }
                    for (int i = 1; axisLineIndex - i >= 0; ++i)
                    {
                        horizontalGridLabels[axisLineIndex - i].Visibility = i % labeledGridLinesStep == 0 ? Visibility.Visible : Visibility.Collapsed;
                    }
                }
            }

            if (verticalGridLabels.Count > 0 && HorizontalLabelStrip.Position == HorizontalLabelStripPosition.AxisGridLine && VerticalLabelStrip.Position == VerticalLabelStripPosition.AxisGridLine && HorizontalGrid.AxisLine != null && VerticalGrid.AxisLine != null)
            {
                verticalGridLabels.First(tb => (double)tb.Tag == 0).Visibility = Visibility.Collapsed;
            }

            List<List<double>> plotsUnscaledValues = new List<List<double>>();
            List<double> argumentValues = new List<double>();
            foreach (ExplicitFunctionPlotItem plot in Items)
            {
                List<double> currentPlotUnscaledValues = new List<double>();
                for (double functionArgument = logicalFrameXMin; functionArgument < logicalFrameXMax; functionArgument += LogicalFrameWidth / Math.Max(root.ActualWidth / 2, 2))
                {
                    double scaledFunctionArgument = HorizontalScale.GetScaledValue(functionArgument);
                    double functionResult = plot.DataSet.GetValue(scaledFunctionArgument);
                    double unscaledFunctionResult = VerticalScale.GetUnscaledValue(functionResult);
                    if (FitLogicalFrameHeight && Items.IndexOf(plot) == 0 && functionArgument == logicalFrameXMin)
                    {
                        LogicalFrameYMin = unscaledFunctionResult - Math.Abs(unscaledFunctionResult * 0.05);
                        LogicalFrameYMax = unscaledFunctionResult + Math.Abs(unscaledFunctionResult * 0.05);
                    }
                    if (unscaledFunctionResult > logicalFrameYMax)
                    {
                        if (FitLogicalFrameHeight)
                        {
                            LogicalFrameYMax = unscaledFunctionResult + Math.Abs(unscaledFunctionResult * 0.05);
                        }
                        else
                        {
                            unscaledFunctionResult = logicalFrameYMax;
                        }
                    }
                    if (unscaledFunctionResult < logicalFrameYMin)
                    {
                        if (FitLogicalFrameHeight)
                        {
                            LogicalFrameYMin = unscaledFunctionResult - Math.Abs(unscaledFunctionResult * 0.05);
                        }
                        else
                        {
                            unscaledFunctionResult = logicalFrameYMin;
                        }
                    }
                    currentPlotUnscaledValues.Add(unscaledFunctionResult);
                    if (Items.IndexOf(plot) == 0)
                    {
                        argumentValues.Add(functionArgument);
                    }
                }
                plotsUnscaledValues.Add(currentPlotUnscaledValues);
            }

            foreach (ExplicitFunctionPlotItem plot in Items)
            {
                if (plot.Visible)
                {
                    if (plot.PlotLine.Visible)
                    {
                        List<Point> currentPointsList = new List<Point>();
                        bool drawPoints = true;
                        for (int argumentIndex = 0; argumentIndex < argumentValues.Count; ++argumentIndex)
                        {
                            if (!double.IsInfinity(argumentValues[argumentIndex]) && !double.IsNaN(argumentValues[argumentIndex]) && !double.IsInfinity(plotsUnscaledValues[Items.IndexOf(plot)][argumentIndex]) && !double.IsNaN(plotsUnscaledValues[Items.IndexOf(plot)][argumentIndex]))
                            {
                                if (drawPoints)
                                {
                                    currentPointsList.Add(FromLogicalToPhysical(argumentValues[argumentIndex], plotsUnscaledValues[Items.IndexOf(plot)][argumentIndex]));
                                    if (plotsUnscaledValues[Items.IndexOf(plot)][argumentIndex] >= logicalFrameYMax || plotsUnscaledValues[Items.IndexOf(plot)][argumentIndex] <= logicalFrameYMin)
                                    {
                                        plot.PlotLine.AddPolyline(currentPointsList);
                                        drawPoints = false;
                                    }
                                }
                                else
                                {
                                    if (plotsUnscaledValues[Items.IndexOf(plot)][argumentIndex] < logicalFrameYMax && plotsUnscaledValues[Items.IndexOf(plot)][argumentIndex] > logicalFrameYMin)
                                    {
                                        currentPointsList = new List<Point>();
                                        if (argumentIndex > 0)
                                        {
                                            currentPointsList.Add(FromLogicalToPhysical(argumentValues[argumentIndex - 1], plotsUnscaledValues[Items.IndexOf(plot)][argumentIndex - 1]));
                                        }
                                        currentPointsList.Add(FromLogicalToPhysical(argumentValues[argumentIndex], plotsUnscaledValues[Items.IndexOf(plot)][argumentIndex]));
                                        drawPoints = true;
                                    }
                                }
                            }
                        }

                        plot.PlotLine.AddPolyline(currentPointsList);
                    }
                }
            }
        }

        protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
        {
            base.OnMouseLeftButtonDown(e);

            if (EnableMousePan)
            {
                Point position = e.GetPosition(this);
                oldCursorPosition = FromPhysicalToLogical(position.X, position.Y);
                arePanMouseButtonsDown = true;
                this.CaptureMouse();
                this.Cursor = Cursors.Arrow;
            }
        }

        protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e)
        {
            base.OnMouseLeftButtonUp(e);

            if (EnableMousePan)
            {
                arePanMouseButtonsDown = false;
                this.ReleaseMouseCapture();
                this.Cursor = Cursors.Arrow;
            }
        }

        protected override void OnMouseMove(MouseEventArgs e)
        {
            base.OnMouseMove(e);

            if (EnableMousePan)
            {
                Point position = e.GetPosition(this);
                Point currentCursorPosition = FromPhysicalToLogical(position.X, position.Y);
                if (arePanMouseButtonsDown && oldCursorPosition != null)
                {
                    double dx = -(currentCursorPosition.X - oldCursorPosition.X);
                    double dy = -(currentCursorPosition.Y - oldCursorPosition.Y);
                    MoveLogicalFrame(dx, dy);
                    Redraw();
                }
            }
        }

        protected void OnMouseWheel(object sender, OnlinePlots.Diagrams.MouseWheelEventArgs e)
        {
            if (EnableMouseZoom)
            {
                if (e.Delta > 0)
                {
                    ZoomLogicalFrame(1 / MouseZoomFactor);
                    Redraw();
                }
                if (e.Delta < 0)
                {
                    ZoomLogicalFrame(MouseZoomFactor);
                    Redraw();
                }
            }
        }

        #region ItemsControl

        protected override DependencyObject GetContainerForItemOverride()
        {
            return new ExplicitFunctionPlotItem();
        }

        protected override bool IsItemItsOwnContainerOverride(object item)
        {
            return item is ExplicitFunctionPlotItem;
        }

        protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
        {
            ExplicitFunctionPlotItem plot = element as ExplicitFunctionPlotItem;
            if (plot != null)
            {
                Items.Add(plot);
            }

            base.PrepareContainerForItemOverride(element, item);
        }

        protected override void ClearContainerForItemOverride(DependencyObject element, object item)
        {
            ExplicitFunctionPlotItem plot = element as ExplicitFunctionPlotItem;
            if (plot != null)
            {
                Items.Remove(plot);
            }

            base.ClearContainerForItemOverride(element, item);
        }

        protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e)
        {
            base.OnItemsChanged(e);

            switch (e.Action)
            {
                case NotifyCollectionChangedAction.Add:
                    e.NewItems.OfType<ExplicitFunctionPlotItem>().ToList().ForEach(item => Items.Add(item));
                    break;
                case NotifyCollectionChangedAction.Remove:
                case NotifyCollectionChangedAction.Reset:
                    if (e.OldItems != null)
                        e.OldItems.OfType<ExplicitFunctionPlotItem>().ToList().ForEach(item => Items.Remove(item));
                    break;
                case NotifyCollectionChangedAction.Replace:
                    e.OldItems.OfType<ExplicitFunctionPlotItem>().ToList().ForEach(item => Items.Remove(item));
                    e.NewItems.OfType<ExplicitFunctionPlotItem>().ToList().ForEach(item => Items.Add(item));
                    break;
            }
        }

        #endregion
    }
}
